package mirrormonkey.state;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.util.concurrent.Callable;
import java.util.concurrent.Future;

import mirrormonkey.state.tools.state.entities.ServerStateSyncBox;
import mirrormonkey.state.tools.state.entities.StateSyncBox;

import org.junit.Test;

import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;

public class StateUpdateTest extends SingleClientTestProto {

	public static final int BOX_COUNT = 3;

	public static final float BASE = -2;

	public static final float DELTA = (Math.abs(BASE * 2) / (BOX_COUNT - 1));

	public final class BoxData {

		public ServerStateSyncBox serverBox;

		public StateSyncBox clientBox;

		public int oldInt = 0;

		public BoxData(int i) {
			serverBox = new ServerStateSyncBox();
			serverApplication.getRootNode().attachChild(serverBox);
			serverBox.setLocalTranslation(BASE + i * DELTA, 0f, 0f);
			serverBox.setLocalRotation(new Quaternion(1, 1, 0, 0));
			serverBox.intField = i;
			serverApplication.coreModule.addMapping(getFirstConnection(),
					serverBox, StateSyncBox.class);
			sleepNoInterrupt(100);
			processEvents();
			clientBox = (StateSyncBox) clientApplication.coreModule.appState.entityProvider
					.getData(serverBox.getData().id).entity;
		}
	}

	public BoxData[] boxes;

	public static final float ROTATION_SPEED = (float) ((Math.PI) / 1000);

	public static final Vector3f AXIS = new Vector3f(1, 0, 1);

	@Override
	public void createBoxes() {
		boxes = new BoxData[BOX_COUNT];
		for (int i = 0; i < boxes.length; i++) {
			boxes[i] = new BoxData(i);
		}
		sleepNoInterrupt(100);
		processEvents();

		sleepNoInterrupt(100);
		processEvents();
	}

	@Override
	public void removeBoxes() {
		for (BoxData d : boxes) {
			serverApplication.coreModule.removeMapping(getFirstConnection(),
					d.serverBox);
		}
		boxes = null;
		sleepNoInterrupt(100);
		processEvents();

		sleepNoInterrupt(100);
		processEvents();
	}

	public void rotateBoxes(long delta) {
		for (int i = 0; i < BOX_COUNT; i++) {
			rotateBox(delta, i);
		}
	}

	public void rotateBox(final long delta, final int i) {
		Future<Object> f = serverApplication.enqueue(new Callable<Object>() {
			@Override
			public Object call() {
				BoxData d = boxes[i];
				Quaternion newRot = d.serverBox.getLocalRotation().clone();
				float rot = newRot.toAngleAxis(new Vector3f());
				rot += (delta * ROTATION_SPEED);
				rot %= (2 * Math.PI);
				newRot = newRot.fromAngleAxis(rot, AXIS);
				d.serverBox.setLocalRotation(newRot);
				return null;
			}
		});
		try {
			f.get();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public void incBoxes() {
		for (BoxData d : boxes) {
			d.clientBox.intField = -1;
			d.serverBox.intField++;
			d.oldInt = d.serverBox.intField;
		}
	}

	@Test
	public void testStateUpdate() {
		long lastTime = System.currentTimeMillis();

		long time = 0;
		while (time <= 10000) {
			long delta = System.currentTimeMillis() - lastTime;
			time += delta;
			lastTime = System.currentTimeMillis();

			rotateBoxes(delta);
			incBoxes();

			sleepNoInterrupt(150);
			processEvents();

			for (BoxData d : boxes) {
				assertEquals(d.serverBox.intField, d.clientBox.intField);
				assertEquals(d.oldInt, d.clientBox.intField);
				assertEquals(d.serverBox.getLocalTranslation(),
						d.clientBox.getLocalTranslation());
				assertEquals(d.serverBox.getLocalRotation(),
						d.clientBox.getLocalRotation());
			}
		}
	}

	@Test
	public void testNullValue() {
		long target = System.currentTimeMillis() + 5000;
		int i = 0;
		while (System.currentTimeMillis() < target) {
			for (BoxData d : boxes) {
				d.serverBox.obj = d.serverBox.obj == null ? Integer.valueOf(i)
						: null;
			}
			i++;

			sleepNoInterrupt(100);
			processEvents();

			for (BoxData d : boxes) {
				assertEquals(d.serverBox.obj, d.clientBox.obj);
			}
		}
	}

	@Test
	public void testValueTrack() {
		long lastTime = System.currentTimeMillis();
		long target = lastTime + 10000;
		int i = 0;
		while (System.currentTimeMillis() < target) {
			int newI = (int) (System.currentTimeMillis() % 500);
			long delta = System.currentTimeMillis() - lastTime;
			lastTime = System.currentTimeMillis();
			if (newI != i) {
				i = newI;
				for (BoxData d : boxes) {
					d.clientBox.resetFlag();
				}
				rotateBoxes(delta);

				sleepNoInterrupt(100);
				processEvents();
			}
			sleepNoInterrupt(5);
			processEvents();
			sleepNoInterrupt(5);
			processEvents();
			sleepNoInterrupt(5);
			processEvents();
			for (BoxData d : boxes) {
				assertEquals(d.serverBox.getLocalRotation(),
						d.clientBox.getLocalRotation());
				// TODO double message send workarround
				assertTrue(d.clientBox.setterCalls + "",
						d.clientBox.setterCalls < 3);
			}
		}
	}

	@Test
	public void testValueTrackNull() {
		long lastTime = System.currentTimeMillis();
		long target = lastTime + 10000;
		int i = 0;
		while (System.currentTimeMillis() < target) {
			int newI = (int) (System.currentTimeMillis() % 500);
			lastTime = System.currentTimeMillis();
			if (newI != i) {
				i = newI;
				for (BoxData d : boxes) {
					d.clientBox.resetFlag();
					d.serverBox.trackedObj = d.serverBox.trackedObj == null ? i
							: null;
				}

				sleepNoInterrupt(100);
				processEvents();
			}
			sleepNoInterrupt(5);
			processEvents();
			sleepNoInterrupt(5);
			processEvents();
			sleepNoInterrupt(5);
			processEvents();
			for (BoxData d : boxes) {
				assertEquals(d.serverBox.trackedObj, d.clientBox.trackedObj);
				// TODO double message send workarround
				assertTrue(d.clientBox.objSetterCalls + "",
						d.clientBox.objSetterCalls < 3);
			}
		}
	}
}
